create-project: Add --branch and cleanup arguments The new --branch option can be used to create the initial branch with a name other than the Git default of master. I also cleaned up the command line parser used by create-branch so most of the heavy work is done by args4j, rather than within the application code. Bug: GERRIT-280 Change-Id: I216f5291095bf528e6e92de3550101e2d41ea4df Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt index 08fbd5c..d1e08b1 100644 --- a/Documentation/cmd-create-project.txt +++ b/Documentation/cmd-create-project.txt
@@ -10,11 +10,12 @@ [verse] 'ssh' -p <port> <host> 'gerrit create-project' \ \--name <NAME> \ -[\--owner <OWNER>] \ +[--branch <REF>] \ +[\--owner <GROUP>] \ [\--description <DESC>] \ [\--submit-type <TYPE>] \ -[\--use-contributor-agreements {true|false}] \ -[\--use-signed-off-by {true|false}] +[\--use-contributor-agreements] \ +[\--use-signed-off-by] DESCRIPTION ----------- @@ -43,6 +44,10 @@ Required; name of the project to create. If name ends with `.git` the suffix will be automatically removed. +\--branch:: + Name of the initial branch in the newly created project. + Defaults to 'master'. + \--owner:: Name of the group which will initially own this repository. The specified group must already be defined within Gerrit. @@ -64,13 +69,13 @@ Action used by Gerrit to submit an approved change to its destination branch. Supported options are: + -* fast-forward-only: produces a strictly linear history. -* merge-if-necessary: create a merge commit when required. -* merge-always: always create a merge commit. -* cherry-pick: always cherry-pick the commit. +* FAST_FORWARD_ONLY: produces a strictly linear history. +* MERGE_IF_NECESSARY: create a merge commit when required. +* MERGE_ALWAYS: always create a merge commit. +* CHERRY_PICK: always cherry-pick the commit. + -Defaults to fast-forward-only. For more details see +Defaults to MERGE_IF_NECESSARY. For more details see link:project-setup.html#submit_type[Change Submit Actions]. \--use-contributor-agreements:: @@ -108,13 +113,13 @@ The remote repository creation is performed by a Bourne shell script: ==== - mkdir -p '/base/project.git' && cd '/base/project.git' && git init --bare + mkdir -p '/base/project.git' && cd '/base/project.git' && git init --bare && git update-ref HEAD refs/heads/master ==== -For this to work successfully the remote system must be able to -run arbitrary shell scripts, and must have `git` in the user's PATH -environment variable. Administrators can run this command by hand -to establish a new empty repository if necessary. +For this to work successfully the remote system must be able to run +arbitrary shell scripts, and must have `git` in the user's PATH +environment variable. Administrators could also run this command line +by hand to establish a new empty repository. SEE ALSO -------- diff --git a/src/main/java/com/google/gerrit/git/PushReplication.java b/src/main/java/com/google/gerrit/git/PushReplication.java index d754e14..77752be 100644 --- a/src/main/java/com/google/gerrit/git/PushReplication.java +++ b/src/main/java/com/google/gerrit/git/PushReplication.java
@@ -174,7 +174,7 @@ return result; } - public void replicateNewProject(Project.NameKey projectName) { + public void replicateNewProject(Project.NameKey projectName, String head) { if (!isEnabled()) { return; } @@ -188,13 +188,12 @@ Iterator<URIish> uriIter = uriList.iterator(); while (uriIter.hasNext()) { - replicateProject(uriIter.next()); + replicateProject(uriIter.next(), head); } } } - - private void replicateProject(final URIish replicateURI) { + private void replicateProject(final URIish replicateURI, final String head) { SshSessionFactory sshFactory = SshSessionFactory.getInstance(); Session sshSession; String projectPath = QuotedString.BOURNE.quote(replicateURI.getPath()); @@ -208,7 +207,8 @@ OutputStream errStream = createErrStream(); String cmd = "mkdir -p " + projectPath + "&& cd " + projectPath - + "&& git init --bare"; + + "&& git init --bare" + + "&& git symbolic-ref HEAD " + QuotedString.BOURNE.quote(head); try { sshSession = sshFactory.getSession(replicateURI.getUser(), diff --git a/src/main/java/com/google/gerrit/git/ReplicationQueue.java b/src/main/java/com/google/gerrit/git/ReplicationQueue.java index b9abd8a..2d71301 100644 --- a/src/main/java/com/google/gerrit/git/ReplicationQueue.java +++ b/src/main/java/com/google/gerrit/git/ReplicationQueue.java
@@ -53,6 +53,7 @@ * that the project will be created at the remote sites as well. * * @param project of the project to be created. + * @param head name HEAD should point at (must be {@code refs/heads/...}). */ - void replicateNewProject(Project.NameKey project); + void replicateNewProject(Project.NameKey project, String head); } diff --git a/src/main/java/com/google/gerrit/server/ssh/SshModule.java b/src/main/java/com/google/gerrit/server/ssh/SshModule.java index e98d1a7..e04486f 100644 --- a/src/main/java/com/google/gerrit/server/ssh/SshModule.java +++ b/src/main/java/com/google/gerrit/server/ssh/SshModule.java
@@ -17,6 +17,7 @@ import static com.google.inject.Scopes.SINGLETON; import com.google.gerrit.client.reviewdb.Account; +import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.PatchSet; import com.google.gerrit.pgm.CmdLineParser; import com.google.gerrit.pgm.OptionHandlerFactory; @@ -27,6 +28,7 @@ import com.google.gerrit.server.config.FactoryModule; import com.google.gerrit.server.config.GerritRequestModule; import com.google.gerrit.server.project.ProjectControl; +import com.google.gerrit.server.ssh.args4j.AccountGroupIdHandler; import com.google.gerrit.server.ssh.args4j.AccountIdHandler; import com.google.gerrit.server.ssh.args4j.PatchSetIdHandler; import com.google.gerrit.server.ssh.args4j.ProjectControlHandler; @@ -104,6 +106,7 @@ factory(CmdLineParser.Factory.class); registerOptionHandler(Account.Id.class, AccountIdHandler.class); + registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class); registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class); registerOptionHandler(ProjectControl.class, ProjectControlHandler.class); }
diff --git a/src/main/java/com/google/gerrit/server/ssh/args4j/AccountGroupIdHandler.java b/src/main/java/com/google/gerrit/server/ssh/args4j/AccountGroupIdHandler.java new file mode 100644 index 0000000..63be4a5 --- /dev/null +++ b/src/main/java/com/google/gerrit/server/ssh/args4j/AccountGroupIdHandler.java
@@ -0,0 +1,57 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.ssh.args4j; + +import com.google.gerrit.client.reviewdb.AccountGroup; +import com.google.gerrit.server.account.GroupCache; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +public class AccountGroupIdHandler extends OptionHandler<AccountGroup.Id> { + private final GroupCache groupCache; + + @SuppressWarnings("unchecked") + @Inject + public AccountGroupIdHandler(final GroupCache groupCache, + @Assisted final CmdLineParser parser, @Assisted final OptionDef option, + @Assisted final Setter setter) { + super(parser, option, setter); + this.groupCache = groupCache; + } + + @Override + public final int parseArguments(final Parameters params) + throws CmdLineException { + final String n = params.getParameter(0); + final AccountGroup group = groupCache.lookup(n); + if (group == null) { + throw new CmdLineException(owner, "Group \"" + n + "\" does not exist"); + } + setter.addValue(group.getId()); + return 1; + } + + @Override + public final String getDefaultMetaVariable() { + return "GROUP"; + } +} diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java b/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java index 8249620..c527536 100644 --- a/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java +++ b/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java
@@ -23,7 +23,6 @@ import com.google.gerrit.client.reviewdb.Project.SubmitType; import com.google.gerrit.git.GitRepositoryManager; import com.google.gerrit.git.ReplicationQueue; -import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.ssh.AdminCommand; import com.google.gerrit.server.ssh.BaseCommand; @@ -32,6 +31,7 @@ import com.google.inject.Inject; import org.kohsuke.args4j.Option; +import org.spearce.jgit.lib.Constants; import org.spearce.jgit.lib.Repository; import java.io.PrintWriter; @@ -40,23 +40,29 @@ /** Create a new project. **/ @AdminCommand final class AdminCreateProject extends BaseCommand { - @Option(name = "--name", required = true, aliases = { "-n" }, usage = "name of project to be created") + @Option(name = "--name", required = true, aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created") private String projectName; - @Option(name = "--owner", aliases = { "-o" }, usage = "name of group that will own the project (defaults to: Administrators)") - private String ownerName; + @Option(name = "--owner", aliases = {"-o"}, usage = "owner of project\n" + + "(default: Administrators)") + private AccountGroup.Id ownerId; - @Option(name = "--description", aliases = { "-d" }, usage = "description of the project") - private String projectDescription; + @Option(name = "--description", aliases = {"-d"}, metaVar = "DESC", usage = "description of project") + private String projectDescription = ""; - @Option(name = "--submit-type", aliases = { "-t" }, usage = "project submit type (F)ast forward only, (M)erge if necessary, merge (A)lways or (C)herry pick (defaults to: F)") - private String submitTypeStr; + @Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n" + + "(default: MERGE_IF_NECESSARY)") + private SubmitType submitType = SubmitType.MERGE_IF_NECESSARY; - @Option(name = "--use-contributor-agreements", aliases = { "--ca" }, usage = "set this to true if project should make the user sign a contributor agreement (defaults to: N)") - private String useContributorAgreements; + @Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required") + private boolean contributorAgreements; - @Option(name = "--use-signed-off-by", aliases = { "--so" }, usage = "set this to true if the project should mandate signed-off-by (defaults to: N)") - private String useSignedOffBy; + @Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required") + private boolean signedOffBy; + + @Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n" + + "(default: master)") + private String branch = Constants.MASTER; @Inject private ReviewDb db; @@ -65,19 +71,11 @@ private GitRepositoryManager repoManager; @Inject - private GroupCache groupCache; - - @Inject private AuthConfig authConfig; @Inject private ReplicationQueue rq; - private AccountGroup.Id ownerId = null; - private boolean contributorAgreements = false; - private boolean signedOffBy = false; - private SubmitType submitType = null; - @Override public void start() { startThread(new CommandRunnable() { @@ -85,6 +83,7 @@ public void run() throws Exception { PrintWriter p = toPrintWriter(out); + ownerId = authConfig.getAdministratorsGroup(); parseCommandLine(); try { @@ -94,16 +93,17 @@ createProject(txn); - Repository repo = repoManager.createRepository(projectName); + Repository repo = repoManager.createRepository(projectName); repo.create(true); + repo.writeSymref(Constants.HEAD, branch); repoManager.setProjectDescription(projectName, projectDescription); txn.commit(); - rq.replicateNewProject(new Project.NameKey(projectName)); + rq.replicateNewProject(new Project.NameKey(projectName), branch); } catch (Exception e) { - p.print("Error when trying to create project: " - + e.getMessage() + "\n"); + p.print("Error when trying to create project: " + e.getMessage() + + "\n"); p.flush(); } @@ -112,12 +112,10 @@ } private void createProject(Transaction txn) throws OrmException { - final Project.NameKey newProjectNameKey = - new Project.NameKey(projectName); + final Project.NameKey newProjectNameKey = new Project.NameKey(projectName); final Project newProject = - new Project(newProjectNameKey, - new Project.Id(db.nextProjectId())); + new Project(newProjectNameKey, new Project.Id(db.nextProjectId())); newProject.setDescription(projectDescription); newProject.setSubmitType(submitType); @@ -127,85 +125,31 @@ db.projects().insert(Collections.singleton(newProject), txn); final ProjectRight.Key prk = - new ProjectRight.Key(newProjectNameKey, - ApprovalCategory.OWN, ownerId); + new ProjectRight.Key(newProjectNameKey, ApprovalCategory.OWN, ownerId); final ProjectRight pr = new ProjectRight(prk); pr.setMaxValue((short) 1); pr.setMinValue((short) 1); db.projectRights().insert(Collections.singleton(pr), txn); final Branch newBranch = - new Branch( - new Branch.NameKey(newProjectNameKey, Branch.R_HEADS + "master")); - + new Branch(new Branch.NameKey(newProjectNameKey, branch)); db.branches().insert(Collections.singleton(newBranch), txn); } - private boolean stringToBoolean(final String boolStr, - final boolean defaultValue) throws Failure { - if (boolStr == null) { - return defaultValue; - } - - if (boolStr.equalsIgnoreCase("FALSE") - || boolStr.equalsIgnoreCase("F") - || boolStr.equalsIgnoreCase("NO") - || boolStr.equalsIgnoreCase("N")) { - return false; - } - - if (boolStr.equalsIgnoreCase("TRUE") - || boolStr.equalsIgnoreCase("T") - || boolStr.equalsIgnoreCase("YES") - || boolStr.equalsIgnoreCase("Y")) { - return true; - } - - throw new Failure(1, "Parameter must have boolean value (true, false)"); - } - private void validateParameters() throws Failure { if (projectName.endsWith(".git")) { - projectName = projectName.substring(0, - projectName.length() - ".git".length()); + projectName = + projectName.substring(0, projectName.length() - ".git".length()); } - if (ownerName == null) { - ownerId = authConfig.getAdministratorsGroup(); - } else { - AccountGroup ownerGroup = groupCache.lookup(ownerName); - if (ownerGroup == null) { - throw new Failure(1, "Specified group does not exist"); - } - ownerId = ownerGroup.getId(); + while (branch.startsWith("/")) { + branch = branch.substring(1); } - - if (projectDescription == null) { - projectDescription = ""; + if (!branch.startsWith(Constants.R_HEADS)) { + branch = Constants.R_HEADS + branch; } - - contributorAgreements = stringToBoolean(useContributorAgreements, false); - signedOffBy = stringToBoolean(useSignedOffBy, false); - - if (submitTypeStr == null) { - submitType = SubmitType.FAST_FORWARD_ONLY; - - } else if (submitTypeStr.equalsIgnoreCase("fast-forward-only")) { - submitType = SubmitType.FAST_FORWARD_ONLY; - - } else if (submitTypeStr.equalsIgnoreCase("merge-if-necessary")) { - submitType = SubmitType.MERGE_IF_NECESSARY; - - } else if (submitTypeStr.equalsIgnoreCase("merge-always")) { - submitType = SubmitType.MERGE_ALWAYS; - - } else if (submitTypeStr.equalsIgnoreCase("cherry-pick")) { - submitType = SubmitType.CHERRY_PICK; - - } else { - throw new Failure(1, "Submit type must be either: fast-forward-only, " - + "merge-if-necessary, merge-always or cherry-pick"); + if (!Repository.isValidRefName(branch)) { + throw new Failure(1, "--branch \"" + branch + "\" is not a valid name"); } } } -